<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

define('ROUTING_FILE_PATH', WEBAPP_DIR.DS."config".DS."routing.xml");

class f_core_Routing
{
	private $url;
	private $urlParams = array();
	private $module;
	private $action;
	private $requestParams = array();
    private $routingFilePath;
	
	private static $instances;

    private function __construct($routingFilePath = null)
    {
        if(is_null($routingFilePath))
        {
            $this->routingFilePath = ROUTING_FILE_PATH;
        }
        else
        {
            if(is_file($routingFilePath))
            {
                $this->routingFilePath = $routingFilePath;
            }
            else
            {
                throw new Exception("No routing file found");
            }
        }
    }

	/**
	 * @return f_core_Routing
	 */
	public static function getInstance($routingFilePath = null)
	{
        if(is_null($routingFilePath))
        {
            $routingFilePath = "default";
        }

		if (!isset(self::$instances[$routingFilePath]))
		{
			self::$instances[$routingFilePath] = new self();
		}
		return self::$instances[$routingFilePath];
	}

	/**
	 * Set the request url we get
	 *
	 * @param string $url
	 * @return f_core_Routing
	 */
	public function setRequestURL($url)
	{
		$this->module = null;
		$this->action = null;

		if(is_string($url) && $url != "")
		{		
			if(substr($url, -1, 1) == "/")
			{
				$url = substr($url, 0, -1);
			}
			
			if(substr($url, 0, 1) == "/")
			{
				$url = substr($url, 1);
			}
			
			$cache = f_core_Cache::getInstance();
			
			$key = "routes/".$url;
		
			if($cache->hasCache($key))
			{
				if($this->loadCache($cache->getCacheContent($key)) > 0)
				{
					return $this;
				}
			}
			
			$this->url = $url;
			$this->urlParams = explode("/", $url);
			$this->parseURL();

			if(!is_null($this->action) && !is_null($this->module))
			{
				$cache->putInCache($key, $this->requestParams);
			}
		}
		
		return $this;
	}

	/**
	 * Parse the current rewrited url
	 */
	private function parseURL()
	{
		$xmlFile = f_xml_Xpath::getInstance($this->routingFilePath);
		$routes = $xmlFile->getElementsFromXpath("route");
		$request = f_core_Request::getInstance();

		/* @var $route f_xml_XmlElement */
		foreach($routes as $route)
		{
			$url = $route->getAttributeValue("url");
				
			$urlValues = explode("/", substr($url,1));
				
			$reqVal = array();
				
			if(count($urlValues) == count($this->urlParams))
			{
				foreach($urlValues as $key => $urlValue)
				{
					if(substr($urlValue,0,1) == ":")
					{
						$urlValues[$key] = "([A-Za-z0-9\-_.]+)";
						$reqVal[substr($urlValue,1)] = $this->urlParams[$key];
					}
				}

				$regexp = implode("~", $urlValues);

				if($regexp != "" && preg_match("/^".$regexp."$/", str_replace("/", "~",$this->url)))
				{
					$params = $route->getSubElements("param");

					// Set params from xml (param tag)
					if(count($params) > 0)
					{
						foreach($params as $param)
						{
							$this->requestParams[$param->getAttributeValue("name")] = $param->getContent();
							$request->setParameter($param->getAttributeValue("name"), $param->getContent());
						}
					}

					// Set params from url
					if(count($reqVal) > 0)
					{
						foreach($reqVal as $name => $val)
						{
							$this->requestParams[$name] = $val;
							$request->setParameter($name, $val);
						}
					}
					break;
				}
			}
		}

		if(!is_null($request->getParameter("module")) && !is_null($request->getParameter("action")))
		{
			$this->module = $request->getParameter("module");
			$this->action = $request->getParameter("action");
		}
		else
		{
			// @todo : redirect to 404 page... ?
			header("HTTP/1.0 404 Not Found");
			die("404 Not Found");
			//			$config = f_loader_Config::getInstance()->getParameters("default");
			//			$this->module = $config['default_module'];
			//			$this->action = $config['default_action'];
		}
	}

	/**
	 * Get the module related to the current url
	 * @return string
	 */
	public function getModule()
	{
		return $this->module;
	}

	/**
	 * Get the action related to the current url
	 * @return string
	 */	
	public function getAction()
	{
		return $this->action;
	}
	
	/**
	 * Parse datas from cache
	 * @param string $cacheContent
	 * @return integer nb of imported parameters
	 */
	private function loadCache($cacheContent)
	{
		$request = f_core_Request::getInstance();
		
		$count = 0;

		if(is_array($cacheContent))
		{
			foreach($cacheContent as $paramName => $content)
			{
				if($paramName == "module") $this->module = $content;
				if($paramName == "action") $this->action = $content;
				$request->setParameter($paramName, $content);
				$count++;
			}
		}
		
		return $count;
	}
	
	/**
	 * Retrieve rewrited url if exists
	 * Can retrieve with a given route rule name or not
	 * 
	 * @example getUrl("homepage", array("lang" => $this->getLang());
	 * @example getUrl(array("module" => "website", "action" => display, "docId" => $this->getUniqueId());
	 * 
	 * @return string
	 */
	public static function getUrl()
	{	
		$args = func_get_args();
		
		// No args => error !
		if(count($args) == 0)
		{
			throw new Exception("No arguments to get URL");
		}
		
		// getUrl(array("module" => "website", "action" => display));
		if(count($args) == 1 && is_array($args[0]))
		{
			return self::getUrlByParameters($args[0]);
		}
		// getUrl("test")
		// getUrl("http://www.myurl.com/index.php?module=test&action=test")
		else if(count($args) == 1 && !is_array($args[0]))
		{
			if(strpos($args[0], "&") == false)
			{
				return self::getUrlByRouteName($args[0]);
			}
			else
			{
				list($preUrl, $postUrl) = explode("?", $args[0]);
				$urlArgs = explode("&", $postUrl);
				
				$parameters = array();
								
				foreach($urlArgs as $arg)
				{
					list($key, $value) = explode("=", $arg);
					$parameters[$key] = $value;
				}	
				
				return self::getUrlByParameters($parameters);
			}			
		}
		// getUrl("default", array("module" => "test", "action" => "test"))
		else if(count($args) == 2)
		{		
			return self::getUrlByRouteName($args[0], $args[1]);
		}
		else
		{
			throw new Exception("Too much arguments to get URL");
		}
	}
	
	/**
	 * Retrieve rewrited url by route name if exist
	 * 
	 * @example getUrlByRouteName("homepage", array("lang" => $this->getLang());
	 * 
	 * @param string $routeName
	 * @param array $args
	 * @return string
	 */
	private static function getUrlByRouteName($routeName, $args = array())
	{
		$xmlFile = f_xml_Xpath::getInstance(ROUTING_FILE_PATH);
		$route = $xmlFile->fromXpath("route[@name='".$routeName."']")->getElement();
		
		if(!is_null($route))
		{
			$ruleName = $route->getAttributeValue("name");
				
            $url = $route->getAttributeValue("url");
            $params = $route->getSubElements("param");

            if(!is_null($params))
            {
                foreach($params as $param)
                {
                    /* @var $param f_xml_XmlElement */
                    $vars[$param->getAttributeValue("name")] = $param->getContent();
                }
                $urlArgs = array_merge($args, $vars);
                return self::formatUrlVars($url, $urlArgs);
            }
            else
            {
                return self::formatUrlVars($url, $args);
            }

            break;
		}

		throw new Exception("No url route found for ".$routeName);
	}
	
	/**
	 * Retrieve rewrited url by arguments
	 * 
	 * @example getUrlByParameters(array("module" => "website", "action" => display, "docId" => $this->getUniqueId()));
	 * 
	 * @param array $parameters
	 * @return string
	 */	
	private static function getUrlByParameters($parameters)
	{
		$xmlFile = f_xml_Xpath::getInstance(ROUTING_FILE_PATH);
		$routes = $xmlFile->getElementsFromXpath("route");		
		
		if(is_null($parameters["module"]) && is_null($parameters["action"]))
		{
			throw new Exception("No module and action defined to find url route");
		}
		
		foreach($routes as $route)
		{
			$nbParams = 0;
			
			/* @var $route f_xml_XmlElement */
			$url = $route->getAttributeValue("url");
			$params = $route->getSubElements("param");
			
			$nbParams = count($params);
			
			$urlValues = explode("/", substr($url,1));
			
			$vars = array();
			
			foreach($urlValues as $urlValue)
			{
				if(substr($urlValue, 0, 1) == ":")
				{					
					$nbParams++;
					$vars[substr($urlValue, 1)] = null;						
				}
			}

			if($nbParams == count($parameters))
			{			
				if(is_array($params))
				{
					foreach($params as $param)
					{
						/* @var $param f_xml_XmlElement */
						$vars[$param->getAttributeValue("name")] = $param->getContent();
					}					
				}
				$check = 0;
				
				foreach($vars as $varName => $var)
				{
					if(array_key_exists($varName, $parameters))
					{
						$check++;
					}
				}
				
				if($check == count($vars))
				{
					return self::formatUrlVars($url, $parameters);
					break;
				}
			}			
		}
		
		foreach($parameters as $key => $parameter)
		{
			$urlArgs[] = $key."=".$parameter;
		}
		
		return "?".join("&", $urlArgs);
	}
	
	/**
	 * Format url with given values
	 * 
	 * @example formatUrlVars("/:module/:action", array("module" => "default", "action" => "display"));
	 *
	 * @param string $url
	 * @param array $args
	 * @return string
	 */
	private static function formatUrlVars($url, $args)
	{
		$urlValues = explode("/", substr($url,1));

		$urlVars = array();
		
		foreach($urlValues as $urlValue)
		{
			if(substr($urlValue, 0, 1) == ":")
			{		
				$value = $args[substr($urlValue,1)];
				
				if(!is_null($value))
				{
					$urlVars[] = $args[substr($urlValue,1)];						
				}
				else
				{
					throw new Exception("Url variable '".$urlValue."' is null");	
				}
			}				
			else
			{
				$urlVars[] = $urlValue;
			}			
		}
		
		return "/".join("/", $urlVars);
	}
	
	public static function formatStringForUrl($string, $transformSlash = false)
	{
		$a = "ÀÁÂÃÄÅàáâãäåÒÓÔÕÖØòóôõöøÈÉÊËèéêëÇçÌÍÎÏìíîïÙÚÛÜùúûüÿÑñ";
		$b = "AAAAAAaaaaaaOOOOOOooooooEEEEeeeeCcIIIIiiiiUUUUuuuuyNn";
		$string = utf8_decode(trim($string));
		$string = strtr($string, utf8_decode($a), $b);
        $string = $transformSlash ? preg_replace('/([^.a-zA-Z0-9_]+)/i', '-', $string) : preg_replace('/([^.a-zA-Z0-9_\/]+)/i', '-', $string);
		return $string;
	}
}